﻿/*              Copyright(c) 2008 Dave Sexton              *
 * http://www.codeplex.com/DocProject                      *
 * http://www.codeplex.com/DocProject/Project/License.aspx *
 ***********************************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.Xml;

namespace DaveSexton.DocToMaml
{
	internal sealed class ConceptualImageManager
	{
		#region Public Properties
		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1802:UseLiteralsWhereAppropriate")]
		public static readonly string ConceptualArtFileName = "__conceptual_art.xml";
		#endregion

		#region Private / Protected
		private readonly Dictionary<string, ConceptualImage> images = new Dictionary<string, ConceptualImage>(StringComparer.OrdinalIgnoreCase);
		private readonly Dictionary<IInput, List<string>> imageKeysByInput = new Dictionary<IInput, List<string>>();
		private readonly List<string> imageIds = new List<string>();
		private IInput currentInput;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="ConceptualImageManager" /> class.
		/// </summary>
		public ConceptualImageManager()
		{
		}
		#endregion

		#region Methods
		public void Clear()
		{
			images.Clear();
			imageIds.Clear();
			imageKeysByInput.Clear();
		}

		public ConceptualImage GetImage(string id, string file, string alternateText)
		{
			bool isNewImage = false;
			ConceptualImage image;

			string key = file + alternateText;

			if (images.ContainsKey(key))
				image = images[key];
			else
			{
				image = new ConceptualImage(file, alternateText, GenerateImageId(id, file));

				images.Add(key, image);
				imageIds.Add(image.Id);

				isNewImage = true;
			}

			if (currentInput != null)
				EnsureImageForCurrentInput(key, isNewImage);

			return image;
		}

		private void EnsureImageForCurrentInput(string key, bool isNewImage)
		{
			if (imageKeysByInput.ContainsKey(currentInput))
			{
				List<string> keys = imageKeysByInput[currentInput];

				if (isNewImage || !keys.Contains(key))
					keys.Add(key);
			}
			else
			{
				List<string> keys = new List<string>(4);
				keys.Add(key);

				imageKeysByInput.Add(currentInput, keys);
			}
		}

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase")]
		private string GenerateImageId(string elementId, string file)
		{
			string baseId;

			if (string.IsNullOrEmpty(elementId))
				baseId = System.IO.Path.GetFileNameWithoutExtension(file);
			else
				baseId = elementId;

			baseId = baseId.ToLowerInvariant();

			string id = baseId;
			int i = 0;

			while (imageIds.Contains(id))
				id = baseId + ++i;

			return id;
		}

		public int GetImageCountForInput(IInput input)
		{
			if (imageKeysByInput.ContainsKey(input))
				return imageKeysByInput[input].Count;
			else
				return 0;
		}

		public int GetImageCountForInputs(IEnumerable<IInput> inputs)
		{
			List<ConceptualImage> uniqueImages = new List<ConceptualImage>();

			foreach (IInput input in inputs)
			{
				foreach (ConceptualImage image in GetImagesForInput(input))
				{
					if (!uniqueImages.Contains(image))
						uniqueImages.Add(image);
				}
			}

			return uniqueImages.Count;
		}

		public void BeginConversionContext(IInput input)
		{
			if (input == null)
				throw new ArgumentNullException("input");

			if (imageKeysByInput.ContainsKey(input))
				imageKeysByInput[input].Clear();

			currentInput = input;
		}

		public void EndConversionContext()
		{
			currentInput = null;
		}

		private IEnumerable<ConceptualImage> GetImagesForAllInputs()
		{
			return GetImagesForInputs(imageKeysByInput.Keys);
		}

		private IEnumerable<ConceptualImage> GetImagesForInputs(IEnumerable<IInput> inputs)
		{
			List<ConceptualImage> returnedImages = new List<ConceptualImage>(128);

			return GetImagesForInputsRecursive(returnedImages, inputs);
		}

		private IEnumerable<ConceptualImage> GetImagesForInputsRecursive(List<ConceptualImage> returnedImages, IEnumerable<IInput> inputs)
		{
			foreach (IInput input in inputs)
			{
				foreach (ConceptualImage image in GetImagesForInput(input))
				{
					if (!returnedImages.Contains(image))
					{
						returnedImages.Add(image);
						yield return image;
					}
				}

				IEnumerable<IInput> subinputs = input.Subinputs;

				if (subinputs != null)
					foreach (ConceptualImage image in GetImagesForInputsRecursive(returnedImages, subinputs))
						yield return image;
			}
		}

		private IEnumerable<ConceptualImage> GetImagesForInput(IInput input)
		{
			if (imageKeysByInput.ContainsKey(input))
				foreach (string key in imageKeysByInput[input])
					yield return images[key];
		}

		public void CreateConceptualArtFile(string targetDirectory)
		{
			if (images.Count == 0)
				return;

			string file = System.IO.Path.Combine(targetDirectory, ConceptualArtFileName);

			XmlWriterSettings settings = new XmlWriterSettings();
			settings.Indent = true;

			using (XmlWriter writer = XmlWriter.Create(file, settings))
			{
				// NOTE: Since the input of a conversion can be changed by the user, it's possible that the image dictionary 
				// may contain images that no longer exist in the final result due to user modification.
				// For this reason, GetImagesForAllInputs is used since it returns the latest images from all inputs.  
				// Although, if the user manually removes or adds an image to the result of a conversion, it will not be included
				// since cached results are simply written to disc without being parsed (except to retrieve the GUID if the user updates the result 
				// in case the GUID was changed manually).
				// TODO: Reparse results that are updated manually by the user to gather the images in case they have changed.
				WriteConceptualArtFile(writer, GetImagesForAllInputs());
			}
		}

		public string CreateConceptualArtFileXml()
		{
			using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
			{
				XmlWriterSettings settings = new XmlWriterSettings();
				settings.Indent = true;

				using (XmlWriter writer = XmlWriter.Create(stream, settings))
				{
					WriteConceptualArtFile(writer, GetImagesForAllInputs());
				}

				return Encoding.UTF8.GetString(stream.GetBuffer());
			}
		}

		public string CreateConceptualArtFileXml(IInput input)
		{
			using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
			{
				XmlWriterSettings settings = new XmlWriterSettings();
				settings.Indent = true;

				using (XmlWriter writer = XmlWriter.Create(stream, settings))
				{
					WriteConceptualArtFile(writer, GetImagesForInput(input));
				}

				return Encoding.UTF8.GetString(stream.GetBuffer());
			}
		}

		public string CreateConceptualArtFileXml(IEnumerable<IInput> inputs)
		{
			using (System.IO.MemoryStream stream = new System.IO.MemoryStream())
			{
				XmlWriterSettings settings = new XmlWriterSettings();
				settings.Indent = true;

				using (XmlWriter writer = XmlWriter.Create(stream, settings))
				{
					WriteConceptualArtFile(writer, GetImagesForInputs(inputs));
				}

				return Encoding.UTF8.GetString(stream.GetBuffer());
			}
		}

		private static void WriteConceptualArtFile(XmlWriter writer, IEnumerable<ConceptualImage> conceptualImages)
		{
			writer.WriteStartDocument();
			writer.WriteStartElement("root");

			foreach (ConceptualImage image in conceptualImages)
			{
				writer.WriteStartElement("item");
				writer.WriteAttributeString("id", image.Id);

				writer.WriteStartElement("image");
				writer.WriteAttributeString("file", System.IO.Path.GetFileName(image.File));

				if (!string.IsNullOrEmpty(image.AlternateText))
					writer.WriteElementString("altText", image.AlternateText);

				writer.WriteEndElement();
				writer.WriteEndElement();
			}

			writer.WriteEndElement();
			writer.WriteEndDocument();
		}
		#endregion
	}
}
